
#include <vector>
#include "Utils.h"
#include <opencv2\highgui\highgui.hpp>
#include <opencv2\imgproc\imgproc.hpp>
#include <opencv2\core\core.hpp>
#include <iostream>
#include "SimpleBlobsLib.h"
#include "Templates.h"



#define GETCENTERX(rr) (rr.x + rr.width/2)
#define GETCENTERY(rr) (rr.y + rr.height/2)

void remove_blobs_invalidos(cv::Mat img, const cv::Rect &roi,std::vector<cv::Rect>&rects)
{

	if(rects.size()<=4)
		return;
	//for(int i = 0 ; i < rects.size();i++)
	for(auto it = rects.begin();it != rects.end();it++)
	{
		auto & rect = *it;
		const int centerX = rect.x+ rect.width/2;
		const int centerY = rect.y+ rect.height/2;
		const cv::Point pt(centerX,centerY);
		if(!pt.inside(roi))
		{
			img(rect) = 0;
			rects.erase(it);
			it = rects.begin();
		}
	}



	
	int rectsAntes = rects.size();
	std::sort(rects.begin(),rects.end(),[](const cv::Rect& r0,const cv::Rect& r1){return r0.area() < r1.area();});
	while(rects.size()> 4)
	{
		rects.erase(rects.begin());

	}
	
	if(rectsAntes!=4)
	{
		cv::Mat tmpimg = img.clone();
		Utils::DrawObjectRects(rects,tmpimg,127);
		cv::imshow("antes",tmpimg);

		cv::Mat tmpimg2 = img.clone();
		Utils::DrawObjectRects(rects,tmpimg2,127);
		cv::imshow("depois",tmpimg2);

		cv::Mat c3 = img.clone();		
		cv::rectangle(c3,roi,cv::Scalar(200));
		cv::imshow("roi",c3);

		cv::waitKey();
	}

	

	std::sort(rects.begin(),rects.end(),[](const cv::Rect& r0,const cv::Rect& r1){return r0.x < r1.x;});

}

std::string reconhece_caracteres(const cv::Mat&img,const std::vector<cv::Rect>&blobs,TemplateMatching::Templates & templates)
{
	
	cv::Size maxTemplateSize = templates.GetMaximumTemplateSize();
	
	if(maxTemplateSize.width%2==0)
		maxTemplateSize.width = maxTemplateSize.width +1;
	if(maxTemplateSize.height%2==0)
		maxTemplateSize.height = maxTemplateSize.height +1;

	cv::Mat resultImg(maxTemplateSize.width,maxTemplateSize.height,0);
	
	typedef std::map<int,double>  MapaRespostas;	
	typedef std::pair<int,double>  ParIndiceResposta;
	typedef std::vector<ParIndiceResposta>  VetorRespostas;

	std::string resposta = "";
	cv::Mat imChar(maxTemplateSize,0);

	cv::Rect curRect;
	int templateId= 0;
	try
	{
		for(size_t i = 0 ; i < blobs.size(); i++)
		{
			curRect = blobs[i];
			VetorRespostas respostas;
			cv::Rect charRoi (blobs[i].x-maxTemplateSize.width/2,blobs[i].y-maxTemplateSize.height/2,maxTemplateSize.width,maxTemplateSize.height);

			resultImg = cv::Mat::zeros(maxTemplateSize.width,maxTemplateSize.height,0);
		
			cv::Rect imCharRect((imChar.cols - blobs[i].width)/2,(imChar.rows-blobs[i].height)/2,blobs[i].width,blobs[i].height);
			imChar = 0;
			img(blobs[i]).copyTo(imChar(imCharRect) );
	//		cv::imshow("r",imChar);
		//	cv::waitKey();
			for(size_t ti = 0 ; ti < templates.GetNumberOfTemplates();ti++)
			{		
				templateId = ti;
				auto templateImg = templates.GetImage(ti);			
			
			
				cv::matchTemplate(imChar,templateImg,resultImg,CV_TM_CCORR);

				//cv::imwrite("d:\\"+ std::to_string((long long)ti)+".bmp",resultImg);
				double min,max;
				cv::minMaxLoc(resultImg,&min,&max);

				respostas.push_back(ParIndiceResposta(ti,max));
			
			}
		
			std::sort(respostas.begin(),respostas.end(),[](const ParIndiceResposta&p1,const ParIndiceResposta&p2){return p1.second < p2.second;});

			resposta += templates.GetChar(respostas.rbegin()->first);
		}
	}
	catch(std::exception &ex)
	{
		throw std::exception("erro durante reconhecimento");
	}
	return resposta;
}

void MergeCharactersBlobs(const std::vector<cv::Rect>&rects)
{
	std::vector<cv::Rect>out = rects;

//	std::sort(out.begin(),out.end(),[](const cv::Rect& r0,const cv::Rect&r1){ return r0.area() > r1.area(); });

	for(int i = 0; i <  out.size()-1; i++)
	{
		auto &r0 = out[i];
		for(int j = i+1 ; j < out.size();j++)
		{
			auto& r1 = out[j];

			if(r1.x+ r1.width <= r0.x + r0.width && r1.x >= r0.x )
			{
			//	cv::Rect newRect(r0.x,std::min(r1.y,r0.y),r0.x+r0.
			}

		}
		
	}


	return  ;
}

int localiza_blobs(const cv::Mat &img,std::vector<cv::Rect>&blobRects)
{

	Blobs::BlobList blobs;

	blobs.FindBlobs(img,0);

	blobs.MergeNearest(3,35,32,35);

	blobRects = blobs.GetBlobRects();

	cv::Mat mat = img.clone();
	Utils::DrawObjectRects(blobRects,mat,127);

	static int cont = 0;
	//cv::imwrite("D:\\images\\"+std::to_string((long long)cont) + ".bmp",mat);
	cont ++;
	//cv::imshow("n",mat);
	//cv::imshow("n",mat);
//	cv::waitKey();
//	while(blobRects.size() > 4)
	{
//		GetClosestRect(blobRects);
	}


	return 1;
}

//cv::Mat 
std::vector<cv::Point>detectar_pontos_pretos(const cv::Mat &img,cv::Mat &imgOut)//,cv::Mat &imgOut)
{
	imgOut = cv::Mat (img.rows,img.cols,0);
		
	std::vector<cv::Point>ptsPretos;
	const int threshold = 7;	
	for(int x=0; x<img.cols; x++)
	{
		for(int y=0; y<img.rows; y++)
		{
			cv::Point coordenada(x,y);
			int b = img.at<cv::Vec3b>(coordenada)[0];
			int g = img.at<cv::Vec3b>(coordenada)[1];
			int r = img.at<cv::Vec3b>(coordenada)[2];

			if( r < threshold && g < threshold && b < threshold )
			{
				imgOut.at<uchar>(coordenada) = 255;//( r < threshold && g < threshold && b < threshold ) ? 255:0;
				ptsPretos.push_back(coordenada);
			}
			else imgOut.at<uchar>(coordenada) = 0;




			
		}
	}

//	cv::imshow("0",imgOut);
//	cv::waitKey();

	return ptsPretos;
}

cv::Rect gera_roi_caracteres(const std::vector<cv::Point> &sementes,const cv::Mat& imBase)
{
	int minx = std::min_element(sementes.begin(),sementes.end(),[](const cv::Point&pt0,const cv::Point &pt1){ return pt0.x < pt1.x;})->x;
	int miny = std::min_element(sementes.begin(),sementes.end(),[](const cv::Point&pt0,const cv::Point &pt1){ return pt0.y < pt1.y;})->y;
	int maxx = std::max_element(sementes.begin(),sementes.end(),[](const cv::Point&pt0,const cv::Point &pt1){ return pt0.x < pt1.x;})->x;
	int maxy = std::max_element(sementes.begin(),sementes.end(),[](const cv::Point&pt0,const cv::Point &pt1){ return pt0.y < pt1.y;})->y;
	

	return cv::Rect(minx,miny,maxx-minx,maxy-miny);

/*	int hist[255] = { 0 };
	for(int x=minx; x < maxx; x++)
	{
		for(int y=miny; y < maxy; y++)
		{
			int color = imBase.at<uchar>(cv::Point(x,y)) ;
			hist[color]++;
		}
	}
	
	cv::Scalar mean = cv::mean(imBase(cv::Rect(minx,miny,maxx-minx,maxy-miny)));

	int max = std::max_element(hist,hist+256) - hist;
	*/
}

void detectar_cor_caracteres(const cv::Point semente,cv::Mat& imBase)
{
	int canais[] = {1};
	cv::Rect roi(semente.x-5,semente.y-5,5,10);

	int hist[4] = 
	{
		//imBase.at<uchar>(cv::Point(semente.x,semente.y)),
		imBase.at<uchar>(cv::Point(semente.x-1,semente.y)), 
		imBase.at<uchar>(cv::Point(semente.x+1,semente.y)),
		imBase.at<uchar>(cv::Point(semente.x,semente.y-1)),
		imBase.at<uchar>(cv::Point(semente.x,semente.y+1))
	};
	/*
	for(int x=roi.x; x<roi.x+roi.width; x++)
	{
		for(int y=roi.y; y< roi.y+roi.height; y++)
		{
			int color = imBase.at<uchar>(cv::Point(x,y)) / 10;
			hist[color]++;
		}
	}*/

	int color =* std::min_element(hist,hist+4);

/*	int nbins = 5; // lets hold 256 levels
	int hsize[] = { nbins }; // just one dimension
	float range[] = { 0, 255 };
	const float *ranges[] = { range };
	int chnls[] = {0};
	 //Hold the histogram
	cv::MatND hist;

	cv::calcHist(&imBase(roi), 1, chnls, cv::Mat(), hist,1,hsize,ranges);

	auto val = *std::max_element(hist.data,hist.data+nbins);*/
}

void detectar_possiveis_cores_caracteres(const std::vector<cv::Point>&sementes,cv::Mat& imBase)
{
	for(auto it = sementes.begin();it!=sementes.end();it++)
	{
		auto pt = cv::Point(it->x-1,it->y);

		
	}
}

cv::Mat crescer_regiao_caracteres(const std::vector<cv::Point>&sementes,cv::Mat& imBase)
{
	cv::Mat imBase2;
	cv::cvtColor(imBase,imBase2,::CV_RGB2GRAY);
	for(auto it = sementes.begin();it!=sementes.end();it++)
	{
		//cv::color

	}

	return cv::Mat();
}


cv::Mat calcula_kmedias(const cv::Mat&img,const int clusterCount,const int attempts)//,cv::Mat&imIndexes)//,cv::Mat &bestLabels,cv::Mat &centers)
{	
	//int clusterCount = 10;
	cv::Mat labels;
	//int attempts = 5;
	cv::Mat bestLabels, centers;
	cv::Mat samples(img.rows * img.cols, 3, CV_32F);



	
	for( int y = 0; y < img.rows; y++ )
		for( int x = 0; x < img.cols; x++ )
			for( int z = 0; z < 3; z++)
				samples.at<float>(y + x*img.rows, z) = img.at<cv::Vec3b>(y,x)[z];

	cv::kmeans(samples,clusterCount,bestLabels,cv::TermCriteria(CV_TERMCRIT_ITER|CV_TERMCRIT_EPS, 0.0001, 10000), attempts, cv::KMEANS_PP_CENTERS, centers );

	cv:: Mat imgOut( img.size(), img.type() );
	cv::Mat imIndexes(img.size(),0);
	for( int y = 0; y < img.rows; y++ )
	{
		for( int x = 0; x < img.cols; x++ )
		{
			int cluster_idx = bestLabels.at<int>(y + x*img.rows,0);

			//if(cluster_idx!= color)continue;

			imIndexes.at<uchar>(cv::Point(x,y)) = cluster_idx;

			imgOut.at<cv::Vec3b>(y,x)[0] = centers.at<float>(cluster_idx, 0);
			imgOut.at<cv::Vec3b>(y,x)[1] = centers.at<float>(cluster_idx, 1);
			imgOut.at<cv::Vec3b>(y,x)[2] = centers.at<float>(cluster_idx, 2);
		}
	}

	//cv::imshow("x",imIndexes*20);
	//cv::waitKey();
	return imIndexes;

}

void calcula_histogramas(const cv::Mat &img,cv::Mat &imPts,const cv::Rect &roi,int *histFull,int *histRoi,int *histDiff,const int histSize)
{
	std::fill(histFull,histFull + histSize,0);
	std::fill(histRoi,histRoi + histSize,0);
	std::fill(histDiff,histDiff + histSize,0);
	
	for( int y = 0; y < img.rows; y++ )
	{
		for( int x = 0; x < img.cols; x++ )
		{
			int cluster_idx = img.at<uchar>(cv::Point(x,y));				  
			
			histFull[cluster_idx]++;

			if(x >=roi.x && x <= roi.x + roi.width && y >= roi.y && y <=roi.y + roi.height)
			{
				histRoi[cluster_idx]++;
			}
		}
	}

	for(int i = 0; i < histSize;i++)
	{
		histDiff[i] = histFull[i] - histRoi[i];
	}
}

cv::Mat calcula_hough(const cv::Mat &img,cv::Rect charRoi)
{
	cv::Mat imLines = cv::Mat::zeros(img.size(),0);

	
#if 0
		vector<Vec2f> lines;
		HoughLines(dst, lines, 1, CV_PI/180, 60, 0, 0 );

		for( size_t i = 0; i < lines.size(); i++ )
		{
			float rho = lines[i][0], theta = lines[i][1];
			Point pt1, pt2;
			double a = cos(theta), b = sin(theta);
			double x0 = a*rho, y0 = b*rho;
			pt1.x = cvRound(x0 + 1000*(-b));
			pt1.y = cvRound(y0 + 1000*(a));
			pt2.x = cvRound(x0 - 1000*(-b));
			pt2.y = cvRound(y0 - 1000*(a));
			line( cdst, pt1, pt2, Scalar(0,0,255), 1, CV_AA);
		}
	#else
		std::vector<cv::Vec4i> lines;
		//HoughLinesP(dst, lines, 1, CV_PI/180,50,85, 10 );
		cv::HoughLinesP(img,lines,1,CV_PI/180,50,80, 10 );
		
		for( size_t i = 0; i < lines.size(); i++ )
		{
			cv::Vec4i line = lines[i];		
			
			cv::Point ptStart = cv::Point(line[0], line[1]);
			cv::Point ptEnd = cv::Point(line[2], line[3]);


			if(ptStart.inside(charRoi) && ptEnd.inside(charRoi))
				continue;

			cv::line( imLines, ptStart, ptEnd, cv::Scalar(255), 2, 4);
		}
		
		//cv::Mat imFinal = img-imLines;

		//cv::rectangle(imFinal,charRoi,cv::Scalar(127));

		//cv::threshold(imLines,imLines,225,255,cv::THRESH_BINARY);
		//cv::imshow("29",img);
		//cv::imshow("28",imLines);
		//cv::imshow("30",imFinal);
		//cv::waitKey();
	#endif

	

		return imLines;
}

int analisa_histograma(const cv::Rect roi,int *histDiff,const int *histFullIm,const int *histRoi, const int histSize)
{
	for(int i = 0 ; i < histSize;i++)
	{
		const int roiArea =  roi.width*roi.height;
		if(histRoi[i] < roiArea* 0.2)//|| hist[i] > roiArea * 0.9)
		{
			histDiff[i] =INT_MAX;
		}
	}
			
	int index = std::min_element(histDiff,histDiff+histSize) - histDiff;
				
	return index;


}
cv::Mat exibe_segmento_selecionado(const cv::Mat&img,const int seg_index,const cv::Rect& roi = cv::Rect())
{
	cv::Mat imOut (img.size(),0);

	
	for( int y = 0; y < img.rows; y++ )
	{
		for( int x = 0; x < img.cols; x++ )
		{
			//if(seg_index==img.at<uchar>(cv::Point(x,y)))
			{
				imOut.at<uchar>(cv::Point(x,y)) = (seg_index==img.at<uchar>(cv::Point(x,y)))?255:0;
			}

		}
	}

	return imOut;
}

std::string to_lower(const std::string & str)
{
	std::string strOut;
	strOut.resize(str.size());


	std::transform(str.begin(),str.end(),strOut.begin(),::tolower);
	return strOut;

}

int main( int argc, char** argv )
{
	if(argc < 2)
	{
		return -1;
	}


	std::vector<std::string> args(argv, argv + argc);

	std::vector<std::string>files;
	Utils::GetFilesInDirectory(args[1],"*.BMP",files);

	if(files.size()==0)
		files.push_back(args[1]);

	std::vector<std::string>acertos,erros,excecoes,separacaoErrada;
	TemplateMatching::Templates templates("C:\\Users\\William\\Dropbox\\trabalho\\CARACTERES\\separados\\");	

	
	for(size_t i = 0 ; i < files.size();i++)
	{
		try
		{
			auto &file = files[i];
			cv::Mat img = cv::imread(file),imGray = cv::imread(file,0),imgProc,imPts,imIndexes,centers;
			cv::Mat imgLab;
	
			std::cout << "Processando " + file <<std::endl;
			std::vector<cv::Point>sementes=  detectar_pontos_pretos(img,imPts);
			cv::Rect roi = gera_roi_caracteres(sementes,imGray);

			std::cout << "gaussian blur" <<std::endl;
			cv::GaussianBlur(img,imgProc,cv::Size(3,3),1);

			
			cv::cvtColor(imgProc,imgLab,CV_BGR2Lab);
			//cv::threshold(imgProc,imgProc,100,255,cv::THRESH_BINARY_INV| cv::THRESH_OTSU);
			
			std::cout << "kmedias" <<std::endl;
			const int clusterCount = 8;
			//imgProc = calcula_kmedias(imgProc,clusterCount,5);//,5,imLabels,centers);
			imgProc = calcula_kmedias(imgLab,clusterCount,5);//,5,imLabels,centers);

			std::cout << "hist" <<std::endl;
			int histFull[clusterCount],histRoi[clusterCount],histDiff[clusterCount];
			calcula_histogramas(imgProc,imPts,roi,histFull,histRoi,histDiff,clusterCount);

			int seg_index = analisa_histograma(roi,histDiff,histFull,histRoi,clusterCount);

			cv::Mat imBinary = exibe_segmento_selecionado(imgProc,seg_index,roi);
			std::vector<cv::Rect >blobRects;
			
			
			std::cout << "hough" <<std::endl;
			cv::Mat imLinhas = calcula_hough(imBinary,cv::Rect(roi.x-10,roi.y,roi.width+15,roi.height));
			cv::imshow("23",imgProc*25);
						
			static int cont = 0;
	//		cv::imwrite("D:\\images\\"+std::to_string((long long)cont) + ".bmp",imBinary);
	//		cv::imwrite("D:\\images\\"+std::to_string((long long)cont) + "--.bmp",imBinary -imLinhas);
	//		cv::imwrite("D:\\images\\"+std::to_string((long long)cont) + "-.bmp",imLinhas);
			cont ++;

			cv::Mat final = imBinary - imLinhas;
			std::cout << "blobs" <<std::endl;
			::localiza_blobs(final,blobRects);

			::remove_blobs_invalidos(final,roi,blobRects);

		
			std::cout << "reconhece" <<std::endl;
			std::string res = ::reconhece_caracteres(final,(blobRects),templates);
			std::string resCorreta = Utils::GetFileNameWithoutExtension(file);
/*
			if(blobRects.size()!=4)
			{
				separacaoErrada.push_back(resCorreta);

				Utils::DrawObjectRects(blobRects,final,127);

				cv::imshow("24",final);
				cv::waitKey();
			}*/
			

			std::cout << "Verificando resposta(" + res + ")..."  ;
			if(to_lower(res) == to_lower(resCorreta))
			{
				acertos.push_back(res);
				std::cout << "ok"<< std::endl;
			}
			else
			{
				erros.push_back(resCorreta);
				std::cout << "erro" << std::endl;
			}
			//


			


			//cv::Mat dilated;
			//cv::dilate(imBinary,dilated,cv::getStructuringElement(cv::MORPH_RECT,cv::Size(1,5)));
			//cv::imshow("24",imBinary);
			//cv::imshow("25",dilated);
			//cv::imshow("",final);

			//cv::waitKey();
	
		}
		catch(std::exception & ex)
		{
			std::cout << ex.what();
			excecoes.push_back(files[i]);
		}
		catch(...)
		{
			std::cout << "erro desconhecido";
			excecoes.push_back(files[i]);
		}
	}

	return 1;
}